<?php
//рус
class style_media_http_web extends controller_prototype_class
{

    protected function ControllerInit ()
    {

    }    
    
    protected function PrimaryActionInit ($basicActionName)
    {
     
    }    

    public function PrAction()
    {
        $this->archive_stylePr();        
    }

    /**
		перехватывает обращение к css или js файлу, минимизирует и гзипит его
	*/
    public function archive_stylePrAction()
    {
        list($requestUri) = explode('?', $this->Request()->getRequestUri());
        $filePath = ltrim($requestUri, '/'); 
        $pathParts = pathinfo($filePath);
        
        if (!in_array($pathParts['extension'], MozgCfg::GetProp('global//styleExtensions')))
        {
        	$this->_ctrls->errors->NotFoundPr();
            return;          	
        }
        
        if (!is_file(_ROOT_PATH.$filePath) && !$this->CreateMergedStyleFile(_ROOT_PATH.$filePath))
        {    
        	$this->_ctrls->errors->NotFoundPr();
            return;         		          
        }    
    
        $archFolder = MozgCfg::GetProp('global//styleCachePath'); 
        
        $this->Response()->TurnCompression(true);
        
        $isCompression = $this->Response()->IsCompressionSupported();
        
        if ($isCompression) $archFolder .= 'gz/'; 
        else $archFolder .= 'min/'; 
        
        if ($isCompression) 
        {
        	$gzExtensions = MozgCfg::GetProp('global//gzExtensions');
            $newFilePath = $archFolder.$pathParts['dirname'].'/'.$pathParts['filename'].'.'.$gzExtensions[$pathParts['extension']];
        } 
        else 
        {
            $newFilePath = $archFolder.$filePath;                        
        }
        
        if (!$this->Libs()->dir->Create($archFolder.$pathParts['dirname']))
        {
            $this->_ctrls->errors->NotFoundPr();
            return;               
        }

        $fileType = $this->GetTypeByExt($pathParts['extension']);

        $MinifyFunction = 'Minify' .$fileType;
        
        $contentMethod = 'GetContent' .strtoupper($pathParts['extension']);
        if (!method_exists($this, $contentMethod)) $contentMethod = 'GetContent';   
        $fileContent = $this->$contentMethod(_ROOT_PATH.$filePath);

        $count = 0;
        $fileContent = str_replace('/*skip minifier*/', '', $fileContent, $count);
        if (!$count)
        {
        	$content = $this->Libs()->minifier->$MinifyFunction($fileContent);
        }
        else 
        { 	
        	$content = $fileContent;
        }

        $this->Response()->SetBody($content);

        file_put_contents($newFilePath, $this->Response()->GetResponse());

        $this->GetModule()->GetDecorator()->SetTemplate($fileType);
    } 

    private function GetTypeByExt($ext)
    {
        switch (strtolower($ext)) 
        {
			case 'css' :
				$MinifyFunction = 'CSS';
        		break;
        	case 'js' :
				$MinifyFunction = 'JS';
        		break;
        		
        	case 'htm':
        	case 'html':
				$MinifyFunction = 'HTML';
        		break;
        		
        	default:
        		$MinifyFunction = 'HTML';	
        } 

        return $MinifyFunction; 
    }
    
    /**
		обрабатывает css файл как шаблон
	*/
    private function GetContentCSS($filePath)
    {
        return $this->GetDynamicContent($filePath);        
    }
    
    private function GetContentJS($filePath)
    {
        return $this->GetDynamicContent($filePath);        
    }    
    
    private function GetDynamicContent($filePath)
    {
        $t = $this->GetTemplator()
                  ->SetBasePath()
                  ->CacheTurnOff();
        
        return $t->render($filePath);    	
    }
    
    private function GetContent($filePath)
    {
        return file_get_contents($filePath);        
    }
    
    /**
		содержит номера ревизий файлов стилей(js, css)
	*/
    private $_styleRevisions = array();
    private $_inRevision = array('css', 'js');
    private $_startRevision = 1;
    
    /**
		возвращает номер ревизии
	*/
    public function GetStyleRevision($type)
    {
        $this->LoadStyleRevision();
        if (!isset($this->_styleRevisions[$type])) throw new Exception('Неизвестный тип файла');
        return $this->_styleRevisions[$type];      
    }
    
    private $_isRevisionUpdated = false;
    
    /**
		увеличивает номер ревизии на единицу
	*/
    public function UpdateStyleRevision($type)
    {
        $this->LoadStyleRevision();
        if (!isset($this->_styleRevisions[$type])) throw new Exception('Неизвестный тип файла');
        $this->_styleRevisions[$type]++; 
        $this->_isRevisionUpdated = true;          
    }
    
    private $_isRevisionLoaded = false;
    
    /**
		загружает текущие номера ревизий
	*/
    private function LoadStyleRevision()
    {
        if ($this->_isRevisionLoaded) return;
        $this->_isRevisionLoaded = true;
        $revFile = _CACHE_CONFIG_PATH. '_style_revisions.php';
        if (is_file($revFile)) $this->_styleRevisions = include($revFile); 

        $this->_styleRevisions = $this->InitStyleRevision($this->_styleRevisions);
    }
    
    /**
		сохраняет номера ревизий в постоянном хранилище
	*/
    private function SaveStyleRevision()
    {
        if (!$this->_isRevisionUpdated) return; 
        
        $revFile = _CACHE_CONFIG_PATH. '_style_revisions.php';
        
        $export = '<?php return ' . var_export($this->_styleRevisions, TRUE) . ';';
        if (is_file($revFile) && ! is_writable($revFile)) @chmod($revFile, _CHMOD_FILE);
        if ($this->Libs()->dir->Create(_CACHE_CONFIG_PATH)) file_put_contents($revFile, $export);                
    }    
    
    private function InitStyleRevision($currentRevisions = array())
    {
        $revArr = array_flip($this->_inRevision);
        foreach ($revArr as $k=>&$v) 
        {
            $v = isset($currentRevisions[$k]) ? (int)$currentRevisions[$k] : $this->_startRevision;
        }
        return $revArr;        
    }

    private function SetStyleFileRevision($path, $type)
    {
        $this->LoadStyleRevision();
        if (!isset($this->_styleRevisions[$type])) throw new Exception('Неизвестный тип файла');                    
        if (strpos($path, '?')) $sign = '&';
        else $sign = '?';
        return $path.$sign. '_' .$this->_styleRevisions[$type];
    }        

    static $_startLabelTag = false;
    
    private function GetStartLabelTag()
    {
    	if (!self::$_startLabelTag) 
    	{
    		self::$_startLabelTag = MozgCfg::GetProp('global//uniqueToken');
    	}
    	
    	return self::$_startLabelTag;
    }
    
    private function GetEndLabelTag()
    {
    	return $this->GetStartLabelTag();
    }    
    
    private function GetStyleFileLabel($type, $code)
    {
    	if (!is_array($code)) $code = array($code);
    	if (!sizeof($code)) return '';
    	
    	return '<style_inc' .$this->GetStartLabelTag(). '|' .$type. '::' .implode('-', $code). '|' .$this->GetEndLabelTag(). '/>';
    }

    public function IncludeStyleFile(array $filesByType = null)
    {
    	if (!sizeof($filesByType)) return '';

    	$this->Response()->RegisterOutputFilter('styleParser', array($this, 'ParseStyleLabels'));

    	$fileLabels = '';
   		foreach ($filesByType as $fType => $fileCode)
   		{
   			$fileLabels .= $this->GetStyleFileLabel($fType, $fileCode);
   		}

   		return $fileLabels;
    }

    private $_includedFiles = array();
    
    public function ParseStyleLabels($content)
    {
    	$this->_includedFiles = array();
    	
    	$matches = array();
    	if (!preg_match_all('#(<|&lt;)style_inc' .preg_quote($this->GetStartLabelTag(), '#'). '\|([^|]*?)\|' .preg_quote($this->GetEndLabelTag(), '#'). '(/|\\\/)(>|&gt;)#i', $content, $matches, PREG_SET_ORDER))
    	{
    		return $content;
    	}

    	foreach ($matches as $styleBlock)
    	{
	    	$isEscaped = $isHTMLEntities = false;
	    	if ($styleBlock[3] == '\\/') $isEscaped = true;
	    	if ($styleBlock[4] == '&gt;') $isHTMLEntities = true;    		
    		
    		list($fType, $fCodeList) = explode('::', $styleBlock[2]);

    		$pos = strpos($content, $styleBlock[0]);
    		if (false === $pos) continue;
    		
    		$htmlBlock = $this->GetStyleHTMLBlock($fType, $fCodeList);
    		if ($isHTMLEntities) $htmlBlock = htmlspecialchars($htmlBlock);
    		if ($isEscaped) $htmlBlock = $this->Libs()->str->escapeJSON($htmlBlock);
    		
    		$content = substr_replace($content, $htmlBlock, $pos, strlen($styleBlock[0]));
    	}
   	
    	return $content;
    }
    
    private function GetStyleHTMLBlock($type, $fCodeList)
    {
    	if (!isset($this->_includedFiles[$type])) $this->_includedFiles[$type] = array();
    	
    	$fileCodes = explode('-', $fCodeList);

    	$fileCodes = array_unique($fileCodes);
    	$fileCodes = array_diff($fileCodes, $this->_includedFiles[$type]);
    	$this->_includedFiles[$type] = array_merge($this->_includedFiles[$type], $fileCodes);
    	
    	$styleFiles = MozgCfg::GetProp('global//styleFiles');
    	
    	$realFileCodes = array();
    	foreach ($fileCodes as $fCode) 
    	{
    		if (!isset($styleFiles[$type][$fCode])) continue;
    		$realFileCodes[] = $fCode;
    	}

    	if (!sizeof($realFileCodes)) return '';
    	if (sizeof($realFileCodes) == 1) $path = $styleFiles[$type][$fCode];
    	else
    	{
    		$path = _ROOT_URL.MozgCfg::GetProp('global//styleCacheFolder').implode('-', $realFileCodes). '.' .$type;
    	}
    	
		switch ($type)
		{
			case 'css': return $this->IncludeCSSFile($path);
			case 'js': return $this->IncludeJsFile($path);
			default: return '';
		}
    }

    /**
		возвращает <link> блок для css файлов
	*/
    public function IncludeCSSFile($path)
    {
    	if (!$path) return '';
        return '<link rel="stylesheet" type="text/css" href="' .htmlspecialchars($this->SetStyleFileRevision($path, 'css')). '" />';        
    }
    
    /**
		возвращает <script> блок для js файлов
	*/
    public function IncludeJSFile($path)
    {
    	if (!$path) return '';
        return '<script type="text/javascript" src="' .htmlspecialchars($this->SetStyleFileRevision($path, 'js')). '"></script>';        
    }     
    
    private function CreateMergedStyleFile($filePath)
    {
    	$pathParts = pathinfo($filePath);
    	if (($pathParts['dirname']. '/') != MozgCfg::GetProp('global//styleCachePath')) return false;
    	
    	$fileCodes = explode('-', $pathParts['filename']);
    	
    	$styleFiles = MozgCfg::GetProp('global//styleFiles');
    	
        $realFileCodes = array();
    	foreach ($fileCodes as $fCode) 
    	{
    		if (!isset($styleFiles[$pathParts['extension']][$fCode])) return false;
    		$realFileCodes[] = $fCode;
    	}    	

    	if (!$this->Libs()->dir->Create(MozgCfg::GetProp('global//styleCachePath'))) return false;
    	
    	$mergedContent = '';
    	foreach ($realFileCodes as $fCode)
    	{
    		$mergedContent .= str_replace("\xEF\xBB\xBF", '', $this->GetStyleFileContent($styleFiles[$pathParts['extension']][$fCode])). ' ';
    	}
    	
    	file_put_contents($filePath, $mergedContent);
    	
    	return true;
    }
    
    private function GetStyleFileContent($filePath)
    {
    	if (0 === strpos($filePath, _ROOT_URL))
    	{
    		$filePath = _ROOT_PATH.substr($filePath, strlen(_ROOT_URL));
    	}
    	
		return (string)@file_get_contents($filePath);    	
    }
    
    public function __destruct()
    {
        $this->SaveStyleRevision();
    }
}